/**
 * This file contains the System View class.
 */
package edu.gatech.spacetraderteam3.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.Timer;

/**
 * The System View abstract class.
 * 
 * @author Alexander Ikonomidis
 * @version 1.0
 */
public class ViewSystem extends View {
    /**
     * The timer to use to move and render items in this view.
     */
    private final Timer actionTimer;

    /**
     * The stars in the background of the System Views.
     */
    private static final List<Star> STARS = new ArrayList<Star>();

    /**
     * The orbitals in the System Views.
     */
    private static final List<BackgroundObject> ORBITALS = new ArrayList<BackgroundObject>();

    /**
     * The maximum number of Bacground objects to have.
     */
    private static final int NUM_BACKGROUND_OBJECTS = 5;

    /**
     * Just a random number generator.
     */
    private static final Random RAND = new Random();

    /**
     * The number of stars to be had.
     */
    private int numStars;

    /**
     * The bounds on the stars' brightness.
     */
    private final int maxBrightness = 200, minBrightness = 50;

    /**
     * The stars' x and y velocities.
     */
    private double starVelX = 0, starVelY = 0;

    /**
     * Whether to draw the stars/planets.
     */
    private boolean drawStars = true, drawPlanets = false;

    /**
     * creates the view system
     */
    protected ViewSystem() {
        actionTimer = new Timer(55, new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                doAction();
            }
        });
    }

    /**
     * Sets the background stars to twinkle
     * 
     * @param twinkle
     */
    public void setAction(boolean twinkle) {
        if (twinkle) {
            onWindowResize();
            actionTimer.start();
        } else {
            actionTimer.stop();
        }
    }

    /**
     * Paints the planets on the screen
     * 
     * @param drawPlanets
     */
    public void drawPlanets(boolean drawPlanets) {
        this.drawPlanets = drawPlanets;
    }

    /**
     * Paints the stars on the screen
     * 
     * @param drawStars
     */
    public void drawStars(boolean drawStars) {
        this.drawStars = drawStars;
    }

    /**
     * Sets the speed of each star
     * 
     * @param starVelX
     * @param starVelY
     */
    public void setStarVelocity(double starVelX, double starVelY) {
        this.starVelX = starVelX;
        this.starVelY = starVelY;
    }

    /**
     * Paints the entire view system onto the screen
     * 
     * @param g
     *            graphics object
     */
    public void paint(Graphics2D g) {
        super.paint(g);
        final BufferedImage b = new BufferedImage(wid, hei, BufferedImage.TYPE_INT_ARGB);
        final Graphics2D g2 = (Graphics2D) b.getGraphics();
        g2.setColor(Color.BLACK);
        g2.fillRect(0, 0, wid, hei);
        for (Star star : STARS)
            star.paint(g2);
        if (drawPlanets) {
            for (BackgroundObject planet : ORBITALS) {
                planet.paint(g2);
            }
        }
        g.drawImage(b, 0, 0, null);
        g2.dispose();
    }

    /**
     * Responds to a window resize
     */
    public void onWindowResize() { // TODO: recycle stars, don't create new objects
        super.onWindowResize();
        numStars = wid * hei / 1000;
        STARS.clear();
        while (STARS.size() < numStars) {
            int startingBrightness = (RAND.nextInt(maxBrightness - minBrightness) + minBrightness);
            Star star = new Star(RAND.nextInt(wid), RAND.nextInt(hei), startingBrightness);
            STARS.add(star);
            // star.setVelocity(starVelX, starVelY);
        }
        ORBITALS.clear();
        for (int i = 0; i < NUM_BACKGROUND_OBJECTS; i++) {
            ORBITALS.add(new BackgroundObject());
        }
    }

    /**
     * does the appropriate action based on the boolean values
     */
    protected void doAction() {
        if (drawStars) {
            for (Star star : STARS) {
                star.twinkle();
            }
        }
        if (drawPlanets) {
            final Thread[] threadPool = new Thread[ORBITALS.size()]; // TODO: Use
                                                                     // ThreadPool
            for (int i = 0; i < ORBITALS.size(); i++) {
                threadPool[i] = new Thread(ORBITALS.get(i).getRunnable());
                threadPool[i].start();
            }
            for (Thread thread : threadPool) { // TODO: too many threads!
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        repaint();
    }

    /**
     * The GUI representation of a star
     * 
     * @author Alexander Ikonomidis
     * @version 1.0
     */
    private class Star {
        /**
         * The star's coordinates
         */
        private double x, y;

        /**
         * The incrementation of the glow effect of a star
         */
        private final int glowIncrement = 3;

        /**
         * The star's brightness on the screen
         */
        private int brightness;

        /**
         * Boolean to determine if a star is waxing or waning
         */
        private boolean wax = false;

        /**
         * Creates a star with a certain brightness and location
         * 
         * @param x
         * @param y
         * @param brightness
         */
        private Star(int x, int y, int brightness) {
            this.x = x;
            this.y = y;
            this.brightness = brightness;
            wax = RAND.nextBoolean();
        }

        // public void setVelocity(double xVel, double yVel) {
        // starVelX = xVel;
        // starVelY = yVel;
        // }

        /**
         * Makes a star twinkle
         */
        public void twinkle() {
            x += starVelX;
            y += starVelY;
            if (x < 0) {
                x = wid;
            } else if (x > wid) {
                x = 0;
            }
            if (y < 0) {
                y = hei;
            } else if (y > hei) {
                y = 0;
            }
            brightness += (wax ? 1 : -1) * glowIncrement;
            brightness = Math.min(maxBrightness, Math.max(0, brightness));
            if (brightness <= minBrightness || brightness >= maxBrightness) {
                wax = !wax;
            }
        }

        /**
         * Paints the star
         * 
         * @param g
         *            graphics object
         */
        public void paint(Graphics g) {
            g.setColor(new Color(brightness, brightness, brightness));
            g.drawLine((int) x, (int) y, (int) (x + starVelX), (int) (y));
        }

        /**
         * @return the star's x and y coordinates
         */
        public String toString() {
            return x + " " + y;
        }
    }

    /**
     * An object in the GUI's background
     * 
     * @author Alexander Ikonomidis
     * @version 1.0
     */
    protected class BackgroundObject extends PaintablePlanet {

        /**
         * The amount of wobble effect on a background object
         */
        private final int wobbleMagnitude = 25;

        /**
         * How fast the background object wobbles
         */

        private double wobbleSpeed = 0.02;

        /**
         * Creates a new background object
         */
        private BackgroundObject() {
            super(Assets.PlanetTexture.random().image(), 3);
            texture = Assets.PlanetTexture.random().image();
            x = (int) (wid + Math.random() * wid);
            y = (int) (Math.random() * (hei - 2 * wobbleMagnitude)) + wobbleMagnitude;
            wobbleSpeed += (Math.random() - .5) * wobbleSpeed;
        }

        /**
         * Paints this background object
         * 
         * @param g
         *            graphics object
         */
        public void paint(Graphics2D g) {
            g.drawImage(buff, (int) x, (int) (y + Math.sin(x * wobbleSpeed)
                    * wobbleMagnitude), null);
        }

        /**
         * Moves the background object in the horizontal direction
         */
        public void move() {
            x -= speed;
            if (x + texture.getHeight() < 0) { // use height because the image is
                                               // spherized
                texture = Assets.PlanetTexture.random().image();
                x = (int) (wid + Math.random() * 100);
                y = (int) (Math.random() * hei);
            }
        }

        /**
         * @return the wobble speed and magnitude of this object
         */
        public String toString() {
            return wobbleSpeed + " " + wobbleMagnitude;
        }
    }

    /**
     * @return this is the system view
     */
    public String toString() {
        return "This is the system view";
    }
}
